// This file is a part of Funky C++.
// Copyright (C) 2011  Valentin David
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
#ifndef SUM_HH
# define SUM_HH

# include "signal.h"

namespace funky {

  template <typename Op,
	    typename Duration, typename... Signals>
  struct signal_sum {
  };

  template <typename Op,
	    typename Duration, typename... Signals>
  struct signal_traits<signal_sum<Op, Duration, Signals...> > {
    typedef Duration duration;
    typedef decltype(*(std::declval<signal_sum<Op, Duration, Signals...> >()))
    sample;
  };

  template <typename Op,
	    typename Duration,
	    typename Signal, typename... Signals>
  struct signal_sum<Op, Duration, Signal, Signals...>
    : public signal_sum<Op, Duration, Signals...> {
  private:
    typedef signal_sum<Op, Duration, Signals...> super;
    Signal signal;
    Op op;

  public:
    template <typename First, typename... Other>
    signal_sum(const Op& op, First&& first, Other&&... other):
      super(op, std::forward<Other>(other)...),
      signal(std::forward<First>(first)),
      op(op) {
    }
    signal_sum(const signal_sum& other):
      super(other),
      signal(other.signal),
      op(other.op) {
    };
    signal_sum(signal_sum&& other):
      super(std::move(other)),
      signal(std::move(other.signal)),
      op(std::move(other.op)) {
    }
    signal_sum& operator=(const signal_sum& other) {
      return *this = signal_sum(other);
    }
    signal_sum& operator=(signal_sum&& other) {
      std::swap((super&)*this,
		(super&)other);
      std::swap(signal, other.signal);
      std::swap(op, other.op);
      return *this = signal_sum(other);
    }

    signal_sum& operator+=(const Duration& d) {
      this->super::operator+=(d);
      signal += d;
      return *this;
    }

    signal_sum& operator++() {
      this->super::operator++();
      ++signal;
      return *this;
    }

    signal_sum operator++(int) {
      signal_sum ret{*this};
      ++*this;
      return ret;
    }

    auto operator*() const ->
      decltype(op(*signal, *(std::declval<super>()))) {
      return op(*signal, this->super::operator*());
    }
  };

  template <typename Duration, typename Op, typename Signal>
  struct signal_sum<Op, Duration, Signal> {
  private:
    Signal signal;

  public:
    template <typename First>
    signal_sum(const Op&, First&& first):
      signal(std::forward<First>(first)) {}

    signal_sum(const signal_sum&) = default;
    signal_sum(signal_sum&& other):
      signal(std::move(other.signal)) {}

    signal_sum& operator=(const signal_sum& other) {
      return *this = signal_sum(other);
    }
    signal_sum& operator=(signal_sum&& other) {
      std::swap(signal, other.signal);
      return *this = signal_sum(other);
    }

    signal_sum& operator+=(const Duration& d) {
      signal += d;
      return *this;
    }

    signal_sum& operator++() {
      ++signal;
      return *this;
    }

    signal_sum operator++(int) {
      signal_sum ret{*this};
      ++*this;
      return ret;
    }

    auto operator*() const ->
      decltype(*signal) {
      return *signal;
    }
  };

  struct plus {
    template <typename T, typename U>
    auto operator()(T a, U b) const ->
      decltype(a + b) {
      return a + b;
    }
  };

  template <typename T, typename U,
	    typename RT = typename std::decay<T>::type,
	    typename RU = typename std::decay<U>::type,
	    typename Sample = typename signal_traits<RT>::sample,
	    typename Duration = typename signal_traits<RT>::duration,
	    typename = typename signal_traits<RU>::sample>
  signal_sum<plus, Duration, RT, RU>
  operator+(T&& a,
	    U&& b) {
    return signal_sum<plus, Duration, RT, RU>{
      plus(),
	std::forward<T>(a),
	std::forward<U>(b)
	};
  }

  struct multiplies {
    template <typename T, typename U>
    auto operator()(T a, U b) const ->
      decltype(a * b) {
      return a * b;
    }
  };

  template <typename T, typename U,
	    typename RT = typename std::decay<T>::type,
	    typename RU = typename std::decay<U>::type,
	    typename Sample = typename signal_traits<RT>::sample,
	    typename Duration = typename signal_traits<RT>::duration,
	    typename = typename signal_traits<RU>::sample>
  signal_sum<multiplies, Duration, RT, RU>
  operator*(T&& a, U&& b) {
    return signal_sum<multiplies, Duration, RT, RU>{
      multiplies{},
	std::forward<T>(a),
	std::forward<U>(b)
	};
  }

}

#endif
